import pandas as pd
import datetime
import matplotlib.pyplot as plt
import numpy as np
import warnings
import shutil
from pathlib import Path
from plot_heatmap import plot_heatmap
from select_top_3 import select_top_3
from gen_doc import gen_doc
from gen_html import gen_html
import os

warnings.filterwarnings('ignore')


def backtest(oi_s, oi_l, df, upper, middle, lower, frequency, begin_date):
    assert oi_s < oi_l
    oi_df = pd.read_excel('BTC_Futures_OI_1d.xlsx')
    oi_df = oi_df[1:]
    oi_df.rename(columns={'Unnamed: 0': 'date'}, inplace=True)
    oi_df.columns = ['date', 'price', 'cex_oi', 'cme_oi', 'futures_oi']
    oi_df["oi_momentum"] = oi_df['futures_oi'].rolling(window=oi_s).mean()/oi_df['futures_oi'].rolling(window=oi_l).mean()
    if begin_date == "20220101":
        begin_datetime = datetime.date(2022, 1, 1)
    else:
        raise Exception("error begin_date")
    oi_df = oi_df[oi_df['date'].apply(lambda x: x.date() >= begin_datetime)]
    oi_df = oi_df[oi_df['date'].apply(lambda x: x.date() <= datetime.date(2025, 8, 31))]
    oi_ser = oi_df["oi_momentum"]
    oi_ser.index = oi_df["date"].dt.date
    equity_curve, trade_log_df = bollinger_breakout_strategy(df, upper, middle, lower, oi_ser)
    if trade_log_df.empty:
        print(f"不会发生交易")
        return None, None, None, None, None,\
                None, None, None, None, None,\
                None, None, None, None, None
    trade_log_df.to_csv(f"{frequency}/{begin_date}/trade_log/trade_log_{oi_l}_{oi_s}.csv", index=False)
    hold_btc_equity = hold_btc(df)
    equity_curve_df = pd.DataFrame(equity_curve, columns=['money'])
    equity_curve_df['date'] = df['date'].tolist()
    hold_btc_df = pd.DataFrame({"money": hold_btc_equity})
    hold_btc_df['date'] = df['date'].tolist()
    equity_curve_df['nav'] = equity_curve_df['money'] / equity_curve_df['money'].iloc[0]
    hold_btc_df['nav'] = hold_btc_df['money'] / hold_btc_df['money'].iloc[0]

    if frequency == "1h":
        equity_first_values = equity_curve_df.groupby('date').first()
        equity_first_values = equity_first_values.reset_index(drop=False)
        hold_first_values = hold_btc_df.groupby('date').first()
        hold_first_values = hold_first_values.reset_index(drop=False)
        nav_df = pd.DataFrame({"date": hold_first_values["date"].values,
                               "nav": equity_first_values["nav"].values,
                               "nav(hold_btc)": hold_first_values["nav"].values,
                               "oi_momentum": oi_df["oi_momentum"].values})
        nav_df.to_csv(f"{frequency}/{begin_date}/nav/nav_{oi_l}_{oi_s}.csv", index=False)
        plot_results(equity_first_values,
                     hold_first_values,
                     oi_df, oi_s, oi_l, frequency, begin_date)
        return cal_performance(equity_first_values['nav'], hold_first_values['nav'], trade_log_df)
    else:
        nav_df = pd.DataFrame({"date": hold_btc_df["date"].values,
                               "nav": equity_curve_df["nav"].values,
                               "nav(hold_btc)": hold_btc_df["nav"].values,
                               "oi_momentum": oi_df["oi_momentum"].values})
        nav_df.to_csv(f"{frequency}/{begin_date}/nav/nav_{oi_l}_{oi_s}.csv", index=False)
        plot_results(equity_curve_df, hold_btc_df, oi_df, oi_s, oi_l,frequency, begin_date)
        return cal_performance(equity_curve_df['nav'], hold_btc_df['nav'], trade_log_df)


def cal_performance(nav_series, nav_series_benchmark, trade_log_df):
    # 日收益率
    daily_returns = nav_series.pct_change().dropna()
    benchmark_returns = nav_series_benchmark.pct_change().dropna()

    trading_days = 365
    risk_free_rate = 2.0
    # 总收益率
    total_return = (1 + daily_returns).prod() - 1
    total_return2 = nav_series.iloc[-1]/nav_series.iloc[0]-1
    # 年化收益率
    n_days = len(daily_returns)
    annualized_return = (1 + total_return) ** (trading_days / n_days) - 1
    annualized_return = annualized_return*100
    # 年化波动率
    annualized_volatility = daily_returns.std() * np.sqrt(trading_days)
    annualized_volatility = annualized_volatility*100
    # 夏普比率 risk_free_rate=2.0
    annualized_shape = (annualized_return - risk_free_rate) / annualized_volatility

    # 平均年交易次数
    yearly_count = len(trade_log_df)/(n_days/365)
    # turnover 以百分比的形式呈现 TurnOver = 交易次数×2/策略持续总天数（空仓不操作也算）
    turnover_old = len(trade_log_df)/n_days*100
    turnover = yearly_count/365*100

    # margin 以千分比的形式呈现  Margin=DailyReturn/TurnOver
    # margin = (total_return/n_days)/turnover*1000  这么算是错的啊 这么算的话应该是这样 (1+total_return)**(1/3164)-1
    margin_old = daily_returns.mean()*100/turnover  # 乘以100是为了和turnover保持单位统一
    # 用年化return求daily_return
    daily_return = (annualized_return/100+1)**(1/365)-1
    margin = daily_return*100/turnover

    # fitness, Fitness=Annualized Sharpe×Sqrt(Abs(Margin))
    fitness = annualized_shape * np.sqrt(abs(margin))
    # 先计算fitness再以千分比的形式呈现
    margin = margin*1000

    # max_drawdown
    peak = nav_series.expanding().max()
    drawdown = (nav_series - peak)/peak
    max_drawdown = abs(drawdown.min())
    max_drawdown = max_drawdown*100

    # 超额收益指标
    benchmark_annualized_return = (nav_series_benchmark.iloc[-1]/nav_series_benchmark.iloc[0])**(trading_days / n_days)-1
    benchmark_total_return = (1 + benchmark_returns).prod() - 1
    benchmark_annualized_return2 = (1 + benchmark_total_return) ** (trading_days / n_days) - 1
    benchmark_annualized_return = benchmark_annualized_return * 100
    excess_return_value = annualized_return - benchmark_annualized_return

    # 年化相对收益倍数
    relative_return = annualized_return / benchmark_annualized_return


    # information_ration
    # benchmark_return 是持有BTC的每日return序列 trading_days=365
    excess_returns = daily_returns.reset_index(drop=True) - benchmark_returns.reset_index(drop=True)
    information_ratio_old = excess_returns.mean()/excess_returns.std()*np.sqrt(trading_days)
    excess_returns_percent = excess_returns*100
    information_ratio = excess_return_value/(excess_returns_percent.std()*np.sqrt(trading_days))

    #calmar ratio, Calmar Ratio = Annualized Return/Max Drawdown
    calmar_ratio = annualized_return/abs(max_drawdown)

    # 胜率
    sell_df = trade_log_df[trade_log_df['action'] == 'SELL']
    # trading_counts 一次完整的买卖为一次交易
    trading_counts = len(trade_log_df)/2
    winning_rate = len(sell_df[sell_df["return"] > 0])/trading_counts
    winning_rate = winning_rate*100

    # 盈亏比
    win_df = sell_df[sell_df["return"] > 0]
    lose_df = sell_df[sell_df["return"] < 0]
    profit_loss_ratio = abs(win_df["return"].mean()/lose_df["return"].mean())

    #用凯利公式计算的合理配置仓位
    kelly_fraction = (profit_loss_ratio*winning_rate-(1-winning_rate))/(profit_loss_ratio)

    return (annualized_return, annualized_volatility, annualized_shape, turnover, margin,
            fitness, excess_return_value, relative_return, information_ratio, max_drawdown,
            calmar_ratio, yearly_count, winning_rate, profit_loss_ratio, kelly_fraction)


# 2. 计算布林带
def calculate_bollinger_bands(close_prices, window, k):
    """计算布林带上轨、中轨、下轨"""
    middle = close_prices.rolling(window).mean()
    std = close_prices.rolling(window).std()
    upper = middle + std * k
    lower = middle - std * k
    return upper, middle, lower


def hold_btc(data):
    """一直持有BTC净值曲线"""
    close = data['close']
    position = 0  # 0表示空仓，1表示多仓
    equity = [10000]  # 初始资金10000
    for i in range(len(close)):
        current_price = close.iloc[i]
        if position == 0:
            equity.append(equity[-1])
            position = 1
        else:
            equity.append(equity[-1] * (current_price / pre_price))
        pre_price = current_price
    equity.pop(0)
    return equity


# 3. 策略实现
def bollinger_breakout_strategy(data, upper, middle, lower, io_momentum):
    """布林带突破策略"""
    close = data['close']

    # 初始化仓位和净值
    position = 0  # 0表示空仓，1表示多仓
    equity = [10000]  # 初始资金10000
    trade_log = []
    returns = []  # 每日收益率
    pre_price = 0
    for i in range(len(close)):
        current_price = close.iloc[i]  # 开仓条件：突破上轨且当前无仓位
        date = pd.to_datetime(upper.index[i]).date()
        if position == 0 and current_price > upper.iloc[i] and io_momentum.loc[date] > 1:
            position = 1
            entry_price = current_price
            trade_log.append({
                'date': data.iloc[i].datetime,
                'action': 'BUY',
                'price': entry_price,
                'io_momentum': io_momentum.loc[date]
            })
            equity.append(equity[-1])
        # 平仓条件：回落到中轨且当前有多仓
        elif position == 1 and current_price <= middle.iloc[i]:
            position = 0
            exit_price = current_price
            pct_change = (exit_price - entry_price) / entry_price
            equity.append(equity[-1] * (current_price / pre_price))
            trade_log.append({
                'date': data.iloc[i].datetime,
                'action': 'SELL',
                'price': exit_price,
                'return': pct_change,
                'io_momentum': io_momentum.loc[date]
            })
        # 持有仓位时更新净值
        elif position == 1:
            assert pre_price > 0
            current_value = equity[-1] * (current_price / pre_price)
            equity.append(current_value)
        else:
            equity.append(equity[-1])
        pre_price = current_price
    equity.pop(0)
    # 转换为DataFrame
    equity_curve = pd.Series(equity, index=data['datetime'])
    trade_log_df = pd.DataFrame(trade_log)

    return equity_curve, trade_log_df


# 4. 可视化
def plot_results(equity_curve_df, hold_btc_df,  oi_df, oi_s, oi_l,frequency, begin_date):
    """绘制价格、布林带和净值曲线"""
    # plt.figure(figsize=(14, 10))
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))

    # 净值曲线
    ax1.plot(equity_curve_df['date'], equity_curve_df['nav'], label=f'oi_l={oi_l},oi_s={oi_s}', color='purple')
    ax1.plot(hold_btc_df['date'], hold_btc_df['nav'], label='Hold BTC', color='red')
    ax1.plot(oi_df['date'], oi_df['oi_momentum'], label='OI_Momentum', color='blue')
    ax1.set_title('Strategy Performance')
    ax1.set_ylabel('NAV')
    ax1.set_xlabel('Date')
    ax1.legend()

    ax2.plot(equity_curve_df['date'], equity_curve_df['nav'], label=f'oi_l={oi_l},oi_s={oi_s}', color='purple')
    ax2.plot(hold_btc_df['date'], hold_btc_df['nav'], label='Hold BTC', color='red')
    ax2.plot(oi_df['date'], oi_df['oi_momentum'], label='OI_Momentum', color='blue')
    ax2.set_yscale('log')  # 设置 y 轴为对数尺度
    ax2.set_title('Strategy Performance (Logarithmic Scale)')
    ax2.set_ylabel('NAV(log)')
    ax2.set_xlabel('Date')
    ax2.legend()

    plt.tight_layout()
    plt.savefig(f'{frequency}/{begin_date}/png/oi_l={oi_l},oi_s={oi_s}.png')


def delete_all_folders(current_dir="."):
    """
    删除当前目录下的所有文件夹（保留文件）
    """
    current_path = Path(current_dir)

    if not current_path.exists():
        print("当前路径不存在！")
        return

    folders_deleted = 0
    errors = 0

    # 遍历当前目录下的所有项目
    for item in current_path.iterdir():
        if item.is_dir():
            try:
                # 使用 shutil.rmtree 删除文件夹及其内容
                shutil.rmtree(item)
                print(f"✅ 已删除文件夹: {item.name}")
                folders_deleted += 1
            except Exception as e:
                print(f"❌ 删除文件夹失败 {item.name}: {e}")
                errors += 1

    print(f"\n操作完成！删除了 {folders_deleted} 个文件夹，{errors} 个错误")


def main():
    delete_all_folders()
    for frequency in ["1d", "1h"]:
        for begin_date in ["20220101"]:
            os.makedirs(f"{frequency}/{begin_date}/trade_log")
            os.makedirs(f"{frequency}/{begin_date}/nav")
            os.makedirs(f"{frequency}/{begin_date}/png")
            os.makedirs(f"{frequency}/{begin_date}/heatmap_png")
            os.makedirs(f"{frequency}/{begin_date}/result")
            os.makedirs(f"{frequency}/{begin_date}/top3")
            if frequency == "1h":
                path = "BTC_kline_1h_merge.csv"
            elif frequency == "1d":
                path = "BTC_kline_1d_merge.csv"
            else:
                raise Exception("Frequency must be either 1h or 1d")
            df = pd.read_csv(path)
            df["datetime"] = pd.to_datetime(df["datetime"])
            df["date"] = df["datetime"].dt.date
            df = df[df['date'] >= datetime.date(2021, 1, 1)]
            df = df[df['date'] <= datetime.date(2025, 8, 31)]
            upper, middle, lower = calculate_bollinger_bands(df['close'], 50, 0.5)
            if begin_date == "20220101":
                begin_datetime = datetime.date(2022, 1, 1)
            else:
                raise Exception("error begin_date")
            begin_index = df[df['date'] == begin_datetime].index.values[0]
            df = df[df.index >= begin_index]
            upper = upper[upper.index >= begin_index]
            middle = middle[middle.index >= begin_index]
            lower = lower[lower.index >= begin_index]
            assert df.loc[begin_index, 'date'] == begin_datetime
            upper.index = df['datetime']
            middle.index = df['datetime']
            lower.index = df['datetime']
            assert len(upper) == len(middle) == len(lower) == len(df)
            oi_s_list = [3, 5, 7, 10, 14, 20, 30, 50, 100, 150]
            oi_l_list = [14, 20, 30, 50, 100, 150, 180, 240, 300, 365]
            result = [(x, y) for x in oi_l_list for y in oi_s_list]
            result_df = pd.DataFrame(result, columns=['oi_l', 'oi_s'])

            annualized_shape_list = []
            annualized_return_list = []
            annualized_volatility_list = []
            turnover_list = []
            margin_list = []

            fitness_list = []
            excess_return_list = []
            relative_return_list = []
            information_ratio_list = []
            max_drawdown_list = []

            calmar_ratio_list = []
            yearly_count_list = []
            winning_rate_list = []
            profit_loss_ratio_list = []
            kelly_fraction_list = []
            for oi_l, oi_s in result:
                if oi_l <= oi_s:
                    (annualized_return, annualized_volatility, annualized_shape, turnover, margin,
                     fitness, excess_return, relative_return, information_ratio, max_drawdown,
                     calmar_ratio, yearly_count, winning_rate, profit_loss_ratio, kelly_fraction) = (None, None, None, None, None,
                                                                                                     None,None, None, None, None,
                                                                                                     None, None, None, None, None)
                else:
                    (annualized_return, annualized_volatility, annualized_shape, turnover, margin,
                     fitness, excess_return, relative_return, information_ratio, max_drawdown,
                     calmar_ratio, yearly_count, winning_rate, profit_loss_ratio, kelly_fraction) = backtest(oi_s, oi_l,df, upper, middle, lower, frequency, begin_date)

                annualized_shape_list.append(annualized_shape)
                annualized_return_list.append(annualized_return)
                annualized_volatility_list.append(annualized_volatility)
                turnover_list.append(turnover)
                margin_list.append(margin)

                fitness_list.append(fitness)
                excess_return_list.append(excess_return)
                relative_return_list.append(relative_return)
                max_drawdown_list.append(max_drawdown)
                information_ratio_list.append(information_ratio)

                calmar_ratio_list.append(calmar_ratio)
                yearly_count_list.append(yearly_count)
                winning_rate_list.append(winning_rate)
                profit_loss_ratio_list.append(profit_loss_ratio)
                kelly_fraction_list.append(kelly_fraction)
            result_df['annualized_shape'] = annualized_shape_list
            result_df['annualized_return'] = annualized_return_list
            result_df['annualized_volatility'] = annualized_volatility_list
            result_df['turnover'] = turnover_list
            result_df['margin'] = margin_list

            result_df['fitness'] = fitness_list
            result_df['excess_return'] = excess_return_list
            result_df['relative_return'] = relative_return_list
            result_df['max_drawdown'] = max_drawdown_list
            result_df['information_ratio'] = information_ratio_list

            result_df['calmar_ratio'] = calmar_ratio_list
            result_df['yearly_count'] = yearly_count_list
            result_df['winning_rate'] = winning_rate_list
            result_df['profit_loss_ratio'] = profit_loss_ratio_list
            result_df['kelly_fraction'] = kelly_fraction_list
            result_df.to_csv(f'{frequency}/{begin_date}/result/backtest_result_heatmap.csv', index=False)
            plot_heatmap(frequency=frequency, begin_date=begin_date, x_name="oi_l", y_name="oi_s")
            select_top_3(frequency=frequency, begin_date=begin_date)
            gen_doc(frequency=frequency, begin_date=begin_date, x_name="oi_l", y_name="oi_s")
            gen_html(frequency=frequency, begin_date=begin_date, x_name="oi_l", y_name="oi_s")


if __name__ == '__main__':
    main()
    # backtest(3, 20)
